/**
 * \file    grammar.h
 * \brief   Parser generator.
 * \author  Jérôme LAURENS
 * \version 0.1
 * \date    September 17, 2014
 *
 * Reads a LALR(1) grammar, generates a parser based on a template file.
 * This is based on lemon parser generator, but has strongly been
 * revisited.
 *
 */

#ifndef GRAMMAR_H
#define GRAMMAR_H

#include "common.h"
#include "symbol.h"

LPP_NAMESPACE_BEGIN

class Grammar {
public:
    typedef Grammar __self;
    typedef std::set<RuleRef> RuleRefSet;

    static GrammarRef Make() {
        return std::make_shared<__self>();
    }

    SymbolRefList const & symbols() const {
        return symbols_m;
    }
    SymbolRefList const & lookahead_symbols() const {
        return lookahead_symbols_m;
    }
    SymbolRefList const shift_symbols() const {
        SymbolRefList L = terminals_m;
        //L.push_front(eof_symbol());
        return L;
    }
    SymbolRefList const reduce_symbols() {
        SymbolRefList L = non_terminals_m;
#ifdef WITH_ERROR_SYMBOL
        L.push_front(error_symbol());
#endif
        return L;
    }
    SymbolRefList const & terminals() const {
        return terminals_m;
    }
    SymbolRefList const & non_terminals() const {
        return non_terminals_m;
    }
    SymbolRefList const & multi_terminals() const {
        return multi_terminals_m;
    }
    SymbolIndex number_of_symbols() const {
        return symbols_m.size();
    }
    SymbolIndex number_of_lookahead_symbols() const {
        return lookahead_symbols_m.size()+1;// +1 for the _accept
    }
    SymbolIndex number_of_terminals() const {
        return terminals_m.size();
    }
    SymbolIndex number_of_non_terminals() const {
        return non_terminals_m.size();
    }
    SymbolIndex number_of_multi_terminals() const {
        return multi_terminals_m.size();
    }
    RuleRefSet const & rules() const {
        return rules_m;
    }
    RuleRefList const ordered_rules() const {
        RuleRefList L(rules_m.begin(),rules_m.end());
        std::sort(L.begin(),L.end(),[](RuleRef const & lhs, RuleRef const & rhs)->bool{return lhs->id()<rhs->id();});
        return L;
    }
    RuleIndex number_of_rules() const {
        return rules_m.size();
    }
    RuleRef const & rule_with_id(RuleIndex const i) const {
        for (RuleRef const & R: rules_m) {
            if (R->id()==i) {
                return R;
            }
        }
        throw std::out_of_range((Format("Unknown rule id %1%")%i).str());
    }
    RuleRef const & rule_with_index(RuleIndex const i) const {
        for (RuleRef const & R: rules_m) {
            if (R->index()==i) {
                return R;
            }
        }
        throw std::out_of_range((Format("Unknown rule index %1%")%i).str());
    }
    RuleRef const & accept_rule() const {     /*! The first rule prepended to the grammar is the accept rule.*/
        return rule_with_index(0);
    }
    SymbolRef const & accept_symbol() const {     /*! Start symbol added to the grammar with the initial accept rule, name "_accept".*/
        return accept_symbol_m;
    }
    SymbolRef const & eof_symbol() const {     /*! Token that indicates the end of input, name "$" like regex.*/
        return eof_symbol_m;
    }
#ifdef WITH_ERROR_SYMBOL
    SymbolRef const & error_symbol() {   /*! The error symbol */
        return error_symbol_m;
    }
#endif
    SymbolRef const & first_symbol() {   /*! added by the generator, name "^" */
        if (!first_symbol_m) {
            first_symbol_m = symbol_for_name("^");
        }
        return first_symbol_m;
    }
    SymbolRef const & default_symbol() { /*! default symbol for table compression */
        if (!default_symbol_m) {
            default_symbol_m = symbol_for_name("{default}");
        }
        return default_symbol_m;
    }
    SymbolIndex default_symbol_index() const {
#ifdef WITH_DEFAULT_SYMBOL
        return default_symbol()->index();
#else
        return number_of_lookahead_symbols()+1;
#endif
    }
    SymbolRef const & start_symbol() const {   /*! start symbol as defined by the user */
        if (start_symbol_m) {
            return start_symbol_m;
        } else if (rules_m.size()) {
            return rule_with_index(1)->lhs_symbol();// rule_with_index(0) is the _accept rule.
        } else {
            return start_symbol_m;
        }
    }
    void set_start_symbol_name(String const & __n);
    SymbolRef const & wildcard_symbol() {/*! Token that matches anything */
        if (!wildcard_symbol_m) {
            wildcard_symbol_m = symbol_for_name("*");
        }
        return wildcard_symbol_m;
    }
    void wildcard_symbol(String const & name);
    bool is_wildcard(SymbolRef const & s) const {
        return s == wildcard_symbol_m;
    }

    bool has_fallback() const {
#warning NYI
        return false;
    }
#ifdef WITH_ERROR_SYMBOL
    bool use_error_symbol() const {
#warning NYI
        return use_error_symbol_m;
    }
#endif

    Grammar()
        : accept_symbol_m(symbol_for_name("_accept"))
        , eof_symbol_m(symbol_for_name("$"))
    #ifdef WITH_ERROR_SYMBOL
        , error_symbol_m(symbol_for_name("!"))
    #endif
        {//"!"
    }

    bool has_symbol_for_shortcut(String const & name) const {
        return shortcut_map_m.count(name)>0;
    }
    SymbolRef const & symbol_for_shortcut(String const & shortcut);
    bool has_symbol_for_name(String const & name) const {
        return symbol_map_m.count(name)>0;
    }
    SymbolRef const & symbol_for_name(String const & name) const;
    SymbolRef & symbol_for_name(String const & name, String const & shortcut="");
    SymbolRef const & symbol_at(Index const & i);

    void setup_rule_precedences();

    RuleRef new_rule(SymbolRef const & lhs
                   , String const & alias
                   , Index rule_line);
    bool add_rule(const RuleRef &rule);
    bool promote_to_multi_terminal(const String &name);
    bool promote_to_default(String const & name);

private:
    typedef std::map<String,SymbolRef> SymbolMap; // owns the symbols
    SymbolMap symbol_map_m; // list of all the symbols by name
    SymbolRefList symbols_m;
    SymbolRefList lookahead_symbols_m;
    SymbolRefList terminals_m;
    SymbolRefList non_terminals_m;
    SymbolRefList multi_terminals_m;
    typedef std::map<String,String> ShortcutMap;
    ShortcutMap shortcut_map_m;  /**< the shortcut map. */
    RuleRefSet rules_m;       /* List of all rules, (owned) */
    SymbolRef const & accept_symbol_m;     /*! Start symbol added to the grammar with the initial accept rule, name "_accept".*/
    SymbolRef const & eof_symbol_m;     /*! Token that indicates the end of input, name "$" like regex.*/
#ifdef WITH_ERROR_SYMBOL
    bool use_error_symbol_m;
    SymbolRef const & error_symbol_m;   /*! The error symbol */
#endif
    SymbolRef first_symbol_m;   /*! added by the generator, name "^" */
    SymbolRef default_symbol_m; /*! default symbol for table compression */
    SymbolRef start_symbol_m;   /*! start symbol as defined by the user */
    SymbolRef wildcard_symbol_m;/*! Token that matches anything */

};

LPP_NAMESPACE_END

#endif // GRAMMAR_H
